import { Subject } from 'rxjs';
import { Util } from '../../common/util';
import { AWSContact } from './aws-contact';
import { Device } from '../../services/device';
import {
  DeviceState,
  DeviceErrorType,
  ContactState,
  ContactEventType,
  DeviceEventType,
  ChannelType
} from '../../common/commlib.enums';
import { AWSConnectAdditionalData } from './aws-additionaldata';
import {
  IDevice,
  ICommunicationLibrary,
  IContact
} from '../../common/commlib.interfaces';

declare var connect: any;

export class AWSDevice extends Device {
  private ccpURL: string;
  private ccpDiv: any;
  private agent: any;
  private dependenciesLoaded = false;
  private dependenciesSubject = new Subject<any>();
  private expect_connected: boolean = false;

  private constructor(deviceParameters: any, commlib?: ICommunicationLibrary) {
    super(deviceParameters.id || 'SAP-AWS-CONNECT', commlib);
    Util.addGlobalScripts(
      deviceParameters.globalScripts,
      this.loaded.bind(this)
    );
    this.ccpURL = deviceParameters && deviceParameters.ccpURL;
    this.setParameters();
  }

  public static init(
    deviceParameters: any,
    commlib?: ICommunicationLibrary
  ): IDevice {
    return new AWSDevice(deviceParameters, commlib);
  }

  // For AWS, we mute/unmute in agent level, provide public method to let contact call */
  public mute(): Device {
    this.agent.mute();
    return this;
  }

  public unmute(): Device {
    this.agent.unmute();
    return this;
  }

  public getStatus() {
    return this.status;
  }

  public register(parameters?: any): void {
    this.expect_connected = true;
    if (this.state === DeviceState.OFFLINE) {
      this.changeStateAndNotify(DeviceState.CONNECTED);
      return;
    }
    // If not loaded, just wait and load again
    if (
      !this.dependenciesLoaded ||
      typeof connect === 'undefined' ||
      connect === null
    ) {
      this.dependenciesSubject.subscribe(() => this.register(parameters));
      return;
    }
    this.ccpURL = (parameters && parameters.ccpURL) || this.ccpURL;
    if (!this.ccpURL) {
      console.error('register is called');
      return;
    }
    /***
     * Start to connect to aws ccp
     * TODO: more checkings for URL? Whether we need check whether we have connected?
     */

    const mgr = connect.core.getPopupManager();
    if (mgr) {
      mgr.clear(connect.MasterTopics.LOGIN_POPUP);
    }

    this.ccpDiv = parameters && parameters.ccpDiv;
    if (!this.ccpDiv && typeof document !== 'undefined') {
      this.ccpDiv = document.createElement('awsConnect');
      document.body.appendChild(this.ccpDiv);
      this.ccpDiv.style.visibility = 'hidden';
    }
    this.state = DeviceState.DISCONNECTED;
    connect.core.initCCP(this.ccpDiv, {
      ccpUrl: this.ccpURL,
      loginPopup: true,
      softphone: {
        allowFramedSoftphone: true
      }
    });
    connect.agent(this.subscribeAgentEvent.bind(this));
    connect.contact(this.subscribeContactEvent.bind(this));
  }

  public unregister(): void {
    console.log('unregister is called');
    this.expect_connected = false;
    this.changeStateAndNotify(DeviceState.DISCONNECTED);
    this.setOffline();
  }

  public setState(state: DeviceState): void {
    if (this.state !== state) {
      switch (state) {
        case DeviceState.NOTROUTABLE:
          this.setAvailable(false);
          break;

        case DeviceState.ROUTABLE:
          this.setAvailable(true);
          break;

        case DeviceState.OFFLINE:
          this.setOffline();
          break;

        default:
          console.error(
            'setStte only could be used for routable/offline property'
          );
          break;
      }
    }
  }

  public createContact(_parameters: any): Promise<IContact> {
    return new Promise((resolve, reject) => {
      if (!this.agent) {
        reject(new Error(`Invalid agent`));
      } else {
        resolve(null);
      }
    });
  }

  public joinContact(contactId: string): Promise<IContact> {
    return new Promise((resolve, reject) => {
      if (!this.agent || !contactId) {
        reject(new Error(`Invalid parameters`));
      } else {
        resolve(null);
      }
    });
  }

  private mapState(agentState: any): DeviceState {
    let state: DeviceState;
    switch (agentState.type) {
      case connect.AgentStateType.ROUTABLE:
        state = DeviceState.ROUTABLE;
        break;

      case connect.AgentStateType.NOT_ROUTABLE:
        state = DeviceState.NOTROUTABLE;
        break;

      case connect.AgentStateType.OFFLINE:
        state = DeviceState.OFFLINE;
        break;

      default:
        state = DeviceState.NOTROUTABLE;

        break;
    }
    return state;
  }

  private onError(agent: any) {
    console.error('One error raised by aws');
    this.onError$.next({
      type: DeviceErrorType.ERROR,
      message: 'Error raised by aws with payload agent',
      payload: agent
    });
    this.changeStateAndNotify(DeviceState.NOTROUTABLE);
  }

  private onIncoming(oContact: any) {
    const newState: ContactState = oContact.isConnected()
      ? ContactState.CONNECTED
      : ContactState.INCOMING;
    const newContact = new AWSContact(oContact, this, newState);
    this.contacts.push(newContact);
    this.onNewEvent$.next({
      eventType: ContactEventType.NEW,
      payload: newContact
    });
  }

  private removeContactFromList(inputContact: any) {
    for (let i = 0; i < this.contacts.length; i++) {
      if (this.contacts[i].getId() === inputContact.getContactId()) {
        this.contacts.splice(i, 1);
        return;
      }
    }
  }

  private setAvailable(available: boolean) {
    const routableState = this.agent.getAgentStates().filter((state: any) => {
      if (available) {
        return state.type === connect.AgentStateType.ROUTABLE;
      } else {
        return state.type === connect.AgentStateType.NOT_ROUTABLE;
      }
    })[0];
    this.agent.setState(routableState, {
      success: () => {
        console.log('Set agent status to Available (routable) via Streams');
      },
      failure: () => {
        console.log(
          'Failed to set agent status to Available (routable) via Streams'
        );
      }
    });
  }

  private setOffline() {
    const offlineState = this.agent.getAgentStates().filter((state: any) => {
      return state.type === connect.AgentStateType.OFFLINE;
    })[0];
    if (offlineState) {
      this.agent.setState(offlineState, {
        success: () => {
          console.log('Set agent status to offline (routable) via Streams');
        },
        failure: () => {
          console.log(
            'Failed to set agent status to offline (routable) via Streams'
          );
        }
      });
    }
  }

  private changeStateAndNotify(newState: DeviceState) {
      // Raise Connected if previous state is disconnected
      if (!this.expect_connected && newState === DeviceState.OFFLINE) {
      } else {
        if (newState !== DeviceState.DISCONNECTED) {
          this.expect_connected = true;
          this.state = DeviceState.CONNECTED;
          this.onNewEvent$.next({
            eventType: DeviceEventType.CHANGE,
            payload: this
          });
        }
      }

    this.state = newState;
    this.onNewEvent$.next({
      eventType: DeviceEventType.CHANGE,
      payload: this
    });
  }

  // Get the agent state and forward to parents if needed
  private handleAgentRefresh(agent: any) {
    const newStatusString: string = agent.getStatus() && agent.getStatus().name;
    if (!newStatusString) {
      this.changeStateAndNotify(DeviceState.NOTROUTABLE);
      return;
    }

    if (newStatusString !== this.status) {
      this.status = newStatusString;
      const newAWSState =
        agent.getStatus() || connect.AgentStateType.NOT_ROUTABLE;
      const newDeviceState = this.mapState(newAWSState);
      this.changeStateAndNotify(newDeviceState);
    }
  }

  private onMuteChanged(obj: any) {
    console.log('new state for mute is ' + obj.muted);
  }

  private subscribeAgentEvent(agent: any) {
    this.user = agent.getName() || this.name;
    this.name = this.user + '@AWS-connect';
    this.agent = agent;

    const w = window.open('', connect.MasterTopics.LOGIN_POPUP);
    if (w) {
      w.close();
    }

    agent.onRefresh(this.handleAgentRefresh.bind(this));
    agent.onError(this.onError.bind(this));
    // below are useless?
    agent.onMuteToggle(this.onMuteChanged.bind(this));
  }
  // only watch when event comes and die
  private subscribeContactEvent(contact: any) {
    if (contact.getStatus().type === connect.ContactStateType.ERROR) {
      console.error(
        'A error raised from AWS with status' + contact.getStatus()
      );
      return;
    }

    if (contact.getActiveInitialConnection()) {
      if (contact.isInbound()) {
        this.onIncoming(contact);
      }
    } else {
      contact.onIncoming(this.onIncoming.bind(this));
    }

    // remove from contacts, the contact will notify listener after update its inner state
    contact.onEnded(this.removeContactFromList.bind(this));
    contact.onMissed(this.removeContactFromList.bind(this));
  }

  private setParameters() {
    this.type = ChannelType.PHONE;
    this.state = DeviceState.DISCONNECTED;
    this.user = 'unknown';
    this.additionalData = new AWSConnectAdditionalData();
  }

  private loaded() {
    this.dependenciesLoaded = true;
    this.dependenciesSubject.next();
  }
}
